﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;


namespace ValuationLogic
{
    public class Swap
    {
        private String swapId,userId,counterparty,commodity, currencyUnit, commodityUnit, quoteCode, settlementSpec;
        private bool buySwap;
        private double quantity,fixedPrice, risk, PnL, PV;
        private DateTime dealDate,periodStartDate, periodEndDate;
        private DateTime calDate;

        public Swap()
        {
		    swapId = "-";
			userId = "-";
            counterparty = commodity = currencyUnit = commodityUnit = quoteCode = settlementSpec = "-";
            buySwap = true;
            quantity = fixedPrice = risk = PnL = -1;
            dealDate = periodStartDate = periodEndDate = calDate = new DateTime();
            calDate = new DateTime(9999, 1, 1);
        }

        public Swap(double fixedPrice, double quantity, DateTime periodStartDate, DateTime periodEndDate)
        {
            this.fixedPrice = fixedPrice;
            this.quantity = quantity;
            this.periodStartDate = periodStartDate;
            this.periodEndDate = periodEndDate;
            calDate = new DateTime(9999, 1, 1);
        }

        internal bool calculate(DateTime calDate)
        {
            try
            {
                calRiskExposure(calDate);
                DateTime calDate2 = calDate;
                while (calDate2.DayOfWeek.ToString()[0] == 'S')
                    calDate2 = calDate2.AddDays(-1);
                calDate2 = calDate2.AddDays(-1);
                double PV0 = calPV(calDate2);
                double PV1 = calPV(calDate);
                this.PnL = PV1 - PV0;
                this.calDate = calDate;
            }
            catch
            {
                return false;
            }
            return true;
        }

        internal double calRiskExposure(DateTime calDate)
        {
            //this.calDate = calDate;
            // Risk Exposure Calculation
            int period = (periodEndDate - periodStartDate).Days + 1;
            int workDays = SwapDate.workDays(periodStartDate, period);
            int pastDays = Math.Min((calDate - periodStartDate).Days, period);
            int pastWorkDays = SwapDate.workDays(periodStartDate, pastDays);
            //MessageBox.Show("workDays:" + workDays + "\npastDays:" + pastDays + "\nPastWorkDays:" + pastWorkDays);
            pastDays = Math.Max(pastDays, period);
            risk = quantity * (workDays - pastWorkDays) / workDays;
            return risk;
        }

        private double calPV(DateTime calDate)
        {
            //this.calDate = calDate;
            double floatingPriceXquantity = 0;
            int period = (periodEndDate - periodStartDate).Days + 1;
            int workDays = SwapDate.workDays(periodStartDate, period);
            double quantityEachDay = quantity / workDays;

            DateTime curDate = new DateTime(periodStartDate.Year, periodStartDate.Month, periodStartDate.Day);
            DateTime dateOneMoreMonth, dateTwoMoreMonth;
            int divDay;
            while (SwapDate.biggerMonthYear(periodEndDate, curDate))
            {
                divDay = SwapDate.getMonthDivideDay(curDate.Year, curDate.Month);
                dateOneMoreMonth = curDate.AddMonths(1);
                dateTwoMoreMonth = curDate.AddMonths(2);
                if (SwapDate.smallerMonthYear(calDate, curDate))
                {
                    if (divDay >= curDate.Day)
                        floatingPriceXquantity += Contract.getContractPrice(calDate, dateOneMoreMonth.Year, dateOneMoreMonth.Month) * quantityEachDay
                            * SwapDate.workDays(curDate, divDay - curDate.Day + 1);
                    floatingPriceXquantity += Contract.getContractPrice(calDate, dateTwoMoreMonth.Year, dateTwoMoreMonth.Month)
                        * quantityEachDay * SwapDate.workDays(new DateTime(curDate.Year, curDate.Month, divDay + 1));
                }
                else if (SwapDate.biggerMonthYear(calDate, curDate))
                {
                    floatingPriceXquantity += Settlement.getSettlementPriceSum(curDate) * quantityEachDay;
                }
                else
                {
                    if (curDate.Day < calDate.Day)
                    {
                        // we assume PnL won't change until today is past, not the price is settled, also Risk Exposure
                        // this will effect whether the period is 'calDate.Day - curDate.Day' or 'calDate.Day - curDate.Day + 1'
                        // In addition, curDate.Day must be '1'
                        floatingPriceXquantity += Settlement.getSettlementPriceSum(curDate, calDate.Day - curDate.Day) * quantityEachDay;
                        if (divDay >= calDate.Day)
                            floatingPriceXquantity += Contract.getContractPrice(calDate, dateOneMoreMonth.Year, dateOneMoreMonth.Month) * quantityEachDay
                                * SwapDate.workDays(calDate, divDay - calDate.Day + 1);
                        floatingPriceXquantity += Contract.getContractPrice(calDate, dateTwoMoreMonth.Year, dateTwoMoreMonth.Month)
                            * quantityEachDay * SwapDate.workDays(new DateTime(calDate.Year, calDate.Month, divDay + 1));
                    }
                    else
                    {
                        if (divDay >= curDate.Day)
                            floatingPriceXquantity += Contract.getContractPrice(calDate, dateOneMoreMonth.Year, dateOneMoreMonth.Month) * quantityEachDay
                                * SwapDate.workDays(curDate, divDay - curDate.Day + 1);
                        floatingPriceXquantity += Contract.getContractPrice(calDate, dateTwoMoreMonth.Year, dateTwoMoreMonth.Month)
                            * quantityEachDay * SwapDate.workDays(new DateTime(curDate.Year, curDate.Month, divDay + 1));
                    }
                }
                if (curDate.Day != 1 && curDate.Day == periodStartDate.Day)
                    curDate = new DateTime(periodStartDate.Year, periodStartDate.Month, 1);
                curDate = curDate.AddMonths(1);
            }
            // periodEndDate equalsYearMonth curDate
            divDay = SwapDate.getMonthDivideDay(curDate.Year, curDate.Month);
            dateOneMoreMonth = curDate.AddMonths(1);
            dateTwoMoreMonth = curDate.AddMonths(2);
            if (SwapDate.smallerMonthYear(calDate, curDate))
            {
                floatingPriceXquantity += calContractStartEndSameMonth(calDate, divDay, curDate, dateOneMoreMonth, dateTwoMoreMonth, quantityEachDay);
            }
            else if (SwapDate.biggerMonthYear(calDate, curDate))
            {
                floatingPriceXquantity += Settlement.getSettlementPriceSum(curDate, periodEndDate.Day - curDate.Day + 1) * quantityEachDay;
            }
            else
            {
                // we assume PnL won't change until today is past, not the price is settled, also Risk Exposure
                // this will effect whether the period is 'periodEndDate.Day - curDate.Day' or 'periodEndDate.Day - curDate.Day + 1'
                if (curDate.Day >= calDate.Day)
                {
                    floatingPriceXquantity += calContractStartEndSameMonth(calDate, divDay, curDate, dateOneMoreMonth, dateTwoMoreMonth, quantityEachDay);
                }
                else if (periodEndDate.Day < calDate.Day)
                {
                    floatingPriceXquantity += Settlement.getSettlementPriceSum(curDate, periodEndDate.Day - curDate.Day)* quantityEachDay;
                }
                else
                {
                    floatingPriceXquantity += Settlement.getSettlementPriceSum(curDate, calDate.Day - curDate.Day) * quantityEachDay;
                    if (divDay >= periodEndDate.Day)
                    {
                        floatingPriceXquantity += Contract.getContractPrice(calDate, dateOneMoreMonth.Year, dateOneMoreMonth.Month)
                            * quantityEachDay * SwapDate.workDays(calDate, periodEndDate.Day - calDate.Day + 1);
                    }
                    else
                    {
                        if (divDay >= calDate.Day)
                        {
                            floatingPriceXquantity += Contract.getContractPrice(calDate, dateOneMoreMonth.Year, dateOneMoreMonth.Month) * quantityEachDay
                                * SwapDate.workDays(calDate, divDay - calDate.Day + 1);
                            floatingPriceXquantity += Contract.getContractPrice(calDate, dateTwoMoreMonth.Year, dateTwoMoreMonth.Month) * quantityEachDay
                                * SwapDate.workDays(new DateTime(calDate.Year, calDate.Month, divDay + 1), periodEndDate.Day - divDay);
                        }
                        else
                        {
                            floatingPriceXquantity += Contract.getContractPrice(calDate, dateTwoMoreMonth.Year, dateTwoMoreMonth.Month) * quantityEachDay
                                * SwapDate.workDays(calDate, periodEndDate.Day - calDate.Day + 1);
                        }
                    }
                }
            }
            PV = fixedPrice * quantity - floatingPriceXquantity;
            return PV;
        }

        private double calContractStartEndSameMonth(DateTime calDate, int divDay, DateTime curDate, DateTime dateOneMoreMonth, DateTime dateTwoMoreMonth, double quantityEachDay)
        {
            double floatingPriceXquantity = 0;
            if (divDay < periodEndDate.Day)
            {
                if (divDay >= curDate.Day)
                {
                    floatingPriceXquantity += Contract.getContractPrice(calDate, dateOneMoreMonth.Year, dateOneMoreMonth.Month) * quantityEachDay
                        * SwapDate.workDays(curDate, divDay - curDate.Day + 1);
                    floatingPriceXquantity += Contract.getContractPrice(calDate, dateTwoMoreMonth.Year, dateTwoMoreMonth.Month) * quantityEachDay
                        * SwapDate.workDays(new DateTime(curDate.Year, curDate.Month, divDay + 1), periodEndDate.Day - divDay);
                }
                else
                {
                    floatingPriceXquantity += Contract.getContractPrice(calDate, dateTwoMoreMonth.Year, dateTwoMoreMonth.Month) * quantityEachDay
                        * SwapDate.workDays(curDate, periodEndDate.Day - curDate.Day + 1);
                }
            }
            else
            {
                floatingPriceXquantity += Contract.getContractPrice(calDate, dateOneMoreMonth.Year, dateOneMoreMonth.Month) * quantityEachDay
                    * SwapDate.workDays(curDate, periodEndDate.Day - curDate.Day + 1);
            }
            return floatingPriceXquantity;
        }
		public string getUserId()
        {
            return userId;
        }
		public string getSwapId()
        {
            return swapId;
        }
		public void setUserId(string userId)
        {
            this.userId = userId;
        }
        public void setBuySwap(bool buySwap)
        {
            this.buySwap = buySwap;
        }
		public void setSwapId(string swapId)
        {
            this.swapId = swapId;
        }
        public void setPnL(double PnL)
        {
            this.PnL = PnL;
        }
        public void setRisk(double risk)
        {
            this.risk = risk;
        }

        public void setCounterparty(String counterpartyVal)
        {
            counterparty = counterpartyVal;
        }
        public void setCommodity(String commodityVal)
        {
            commodity = commodityVal;
        }
        public void setCurrencyUnit(String currencyUnitVal)
        {
            currencyUnit = currencyUnitVal;
        }
        public void setCommodityUnit(String commodityUnitVal)
        {
            commodityUnit = commodityUnitVal;
        }
        public void setQuoteCode(String quoteCodeVal)
        {
            quoteCode = quoteCodeVal;
        }
        public void setSettlementSpecVal(String settlementSpecVal)
        {
            settlementSpec = settlementSpecVal;
        }
        public void setBuyFixed(bool buyOrSell)
        {
            buySwap = buyOrSell;
        }
        public void setQuantity(double quantityVal)
        {
            quantity = quantityVal;
        }
        public void setFixedPrice(double fixedPriceVal)
        {
            fixedPrice = fixedPriceVal;
        }
        public void setDealDate(DateTime dealDateVal)
        {
            dealDate = dealDateVal;
        }
        public void setPeriodStartDate(DateTime periodStartDateVal)
        {
            periodStartDate = periodStartDateVal;
        }
        public void setPeriodEndDate(DateTime periodEndDateVal)
        {
            periodEndDate = periodEndDateVal;
        }
        public double getPnL()
        {
            return PnL;
        }
        public String getCounterparty()
        {
            return counterparty;
        }
        public String getCommodity()
        {
            return commodity;
        }
        public String getCurrencyUnit()
        {
            return currencyUnit;
        }
        public String getCommodityUnit()
        {
            return commodityUnit;
        }
        public String getQuoteCode()
        {
            return quoteCode;
        }
        public String getSettlementSpec()
        {
            return settlementSpec;
        }
        public bool isBuySwap()
        {
            return buySwap;
        }
        public double getQuantity()
        {
            return quantity;
        }
        public double getFixedPrice()
        {
            return fixedPrice;
        }
        public DateTime getDealDate()
        {
            return dealDate;
        }
        public DateTime getPeriodStartDate()
        {
            return periodStartDate;
        }
        public DateTime getPeriodEndDate()
        {
            return periodEndDate;
        }
        public DateTime getCalDate()
        {
            return calDate;
        }
        public double getRiskExposure()
        {
            return risk;
        }
    }

    public class SwapDate
    {
        /*public enum MONTH
        {
            JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC
        }
         */

        public static int workDays(int year, int month)
        {
            DateTime date = new DateTime(year, month, 1);
            DateTime dateOneMoreMonth = date.AddMonths(1);
            return workDays(date, (dateOneMoreMonth - date).Days);
        }

        /// <summary>
        /// get the number of workDays from this date to the end of the month of the date
        /// </summary>
        /// <param name="date"></param>
        /// <returns></returns>
        public static int workDays(DateTime date)
        {
            DateTime dateOneMoreMonth = (new DateTime(date.Year, date.Month, 1)).AddMonths(1);
            DateTime dateOne = new DateTime(date.Year, date.Month, 1);
            int monthDays = (dateOneMoreMonth - dateOne).Days;
            return workDays(date, monthDays - date.Day + 1);
        }

        public static int workDays(DateTime date, int period)
        {
            int rtn = 0;
            DateTime cur_date = date;
            for (int i = 0; i < period; i++)
            {
                if (cur_date.DayOfWeek.ToString()[0] != 'S')
                    rtn++;
                cur_date = cur_date.AddDays(1);
            }
            return rtn;
        }

        public static int getMonthDivideDay(int year, int month)
        {
            return getMonthDivideDay(year, month, 25);
        }

        public static int getMonthDivideDay(int year, int month, int day)
        {
            DateTime date = new DateTime(year, month, day);
            while (date.DayOfWeek.ToString()[0] == 'S')
                date = date.AddDays(-1);
            return date.Day;
        }



        public static int getDaysofMonth(DateTime date)
        {
            return getDaysofMonth(date.Year, date.Month);
        }

        public static int getDaysofMonth(int year, int month)
        {
            DateTime date = new DateTime(year, month, 1);
            DateTime dateOneMoreMonth = date.AddMonths(1);
            return (dateOneMoreMonth - date).Days;
        }

        public static bool equalsDay(DateTime date1, DateTime date2)
        {
            return (date1.Day == date2.Day && date1.Month == date2.Month && date1.Year == date2.Year);
        }

        public static bool equalsMonthYear(DateTime date1, DateTime date2)
        {
            return (date1.Year == date2.Year && date1.Month == date2.Month);
        }

        public static bool biggerMonthYear(DateTime bigDate, DateTime smallDate)
        {
            return (bigDate.Year > smallDate.Year || (bigDate.Year == smallDate.Year && bigDate.Month > smallDate.Month));
        }

        public static bool smallerMonthYear(DateTime smallDate, DateTime bigDate)
        {
            return (smallDate.Year < bigDate.Year || (smallDate.Year == bigDate.Year && smallDate.Month < bigDate.Month));
        }
        //public static int pastDays(DateTime cur_date, DateTime old_date)
    }


}
